Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

writer: add CombineWriteSyncers #346

Merged
merged 4 commits into from
Mar 8, 2017
Merged

writer: add CombineWriteSyncers #346

merged 4 commits into from
Mar 8, 2017

Conversation

suyash
Copy link
Contributor

@suyash suyash commented Mar 6, 2017

Allowing functions to take interfaces as parameters allows for better
customization.

For example, to have a writer that also counts the number of writes
specifically made to its instance

type MyWriter struct {
	*os.File
	logcount int
}

func (w *MyWriter) Write(data []byte) (int, error) {
	w.logcount++
	return w.File.Write(data)
}

func (w *MyWriter) Sync() error {
	return w.File.Sync()
}

...

func main() {
	f, err := os.Create(...)
	if err != nil {
		log.Fatal(err)
	}
	defer f.Close()

	w := &MyWriter{f, 0}
	ws, err := zap.OpenWriters(os.Stdout, w)
	if err != nil {
		log.Fatal(err)
	}

	core := zapcore.NewCore(
		zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig()),
		ws, zapcore.DebugLevel)

	logger := zap.New(core)

	...

	fmt.Printf("logged %v times to MyWriter", w.logcount)
}

Before opening your pull request, please make sure that you've:

Thanks for your contribution!

@suyash
Copy link
Contributor Author

suyash commented Mar 6, 2017

cc @peter-edge @akshayjshah

writer.go Outdated
// OpenWriters combines the passed io.Writer objects that are compatible with
// zapcore.WriteSyncer into a locked WriteSyncer. It returns any errors
// encountered.
func OpenWriters(writers ...io.Writer) (zapcore.WriteSyncer, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this function should be here - it somewhat defeats the purpose of static typing. Instead, there is a function in this package that can turn a Writer into a WriteSyncer, and then a user can pass the converted Writer to OpenWriteSyncers

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. In cases where this is needed, I'd much prefer to have users deal with it explicitly.

Please remove this function.

writer.go Outdated
// OpenWriteSyncers combines the passed set of WriteSyncer objects into a locked
// WriteSyncer. It returns any error encountered
func OpenWriteSyncers(writers ...zapcore.WriteSyncer) (zapcore.WriteSyncer, error) {
if len(writers) == 0 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might just return nil here

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer to return a discarder, purely for consistency with New.

writer.go Outdated
if len(writers) == 1 {
return zapcore.Lock(writers[0]), nil
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: there's a lot of newlines here, maybe just remove them :)

writer.go Outdated
// OpenWriters combines the passed io.Writer objects that are compatible with
// zapcore.WriteSyncer into a locked WriteSyncer. It returns any errors
// encountered.
func OpenWriters(writers ...io.Writer) (zapcore.WriteSyncer, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. In cases where this is needed, I'd much prefer to have users deal with it explicitly.

Please remove this function.

writer.go Outdated
// OpenWriteSyncers combines the passed set of WriteSyncer objects into a locked
// WriteSyncer. It returns any error encountered
func OpenWriteSyncers(writers ...zapcore.WriteSyncer) (zapcore.WriteSyncer, error) {
if len(writers) == 0 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer to return a discarder, purely for consistency with New.

writer.go Outdated

if len(writers) == 1 {
return zapcore.Lock(writers[0]), nil
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's actually make this improvement to zapcore.NewMultiWriteSyncer - if it's passed just one argument, it should return that WriteSyncer unchanged. This wrapper can then rely on that behavior.

writer_test.go Outdated

type testWriter struct {
expected []byte
t *testing.T
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For future extensibility, let's make this a testing.TB.

writer_test.go Outdated
w, err := OpenWriters(tw)
if err != nil {
t.Fatalf("OpenWriters failed, error: %v", err)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead, please use the require package, which is already included as part of testify.

require.NoError(t, err, "OpenWriters failed.")

@suyash suyash changed the title writer: add OpenWriters and OpenWriteSyncers writer: add OpenWriteSyncers Mar 6, 2017
@suyash
Copy link
Contributor Author

suyash commented Mar 8, 2017

@akshayjshah ping

Copy link
Contributor

@akshayjshah akshayjshah left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for updating! There's still a little work to do here.

writer.go Outdated
return zapcore.AddSync(ioutil.Discard), nil
}
return zapcore.Lock(zapcore.NewMultiWriteSyncer(writers...)), nil
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove this function; I'd much rather export just Open.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@akshayjshah the whole point of this PR was to get a function in the API that takes interfaces instead of strings as output file paths.

Please see my first comment

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ack, my fault - I confused this with the previously-present OpenWriters.

Since the files/writers are already open, let's rename this to CombineWriteSyncers or ComposeWriteSyncers instead. This function can't fail, so let's also remove the error return value.

@@ -88,6 +88,9 @@ type multiWriteSyncer []WriteSyncer
// NewMultiWriteSyncer creates a WriteSyncer that duplicates its writes
// and sync calls, much like io.MultiWriter.
func NewMultiWriteSyncer(ws ...WriteSyncer) WriteSyncer {
if len(ws) == 1 {
return ws[0]
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs test coverage.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added, Currently simply verifying that passing just one argument to NewMultiWriteSyncer simply returns the argument back.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Excellent.

Allowing functions to take interfaces as parameters allows for better
customization.

For example, to have a writer that also counts the number of writes
specifically made to its instance

```
type MyWriter struct {
	*os.File
	logcount int
}

func (w *MyWriter) Write(data []byte) (int, error) {
	w.logcount++
	return w.File.Write(data)
}

func (w *MyWriter) Sync() error {
	return w.File.Sync()
}

...

func main() {
	f, err := os.Create(...)
	if err != nil {
		log.Fatal(err)
	}
	defer f.Close()

	w := &MyWriter{f, 0}
	ws, err := zap.OpenWriteSyncers(os.Stdout, zapcore.AddSync(w))
	if err != nil {
		log.Fatal(err)
	}

	core := zapcore.NewCore(
		zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig()),
		ws, zapcore.DebugLevel)

	logger := zap.New(core)

	...

	fmt.Printf("logged %v times to MyWriter", w.logcount)
}
```
Copy link
Contributor

@akshayjshah akshayjshah left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small nits remaining, but generally looks good.

Please don't amend your existing commits - it makes reviewing this PR more difficult. Since we always squash before merging, having lots of tiny commits in the PR won't introduce noise on master.

writer.go Outdated
return zapcore.AddSync(ioutil.Discard), nil
}
return zapcore.Lock(zapcore.NewMultiWriteSyncer(writers...)), nil
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ack, my fault - I confused this with the previously-present OpenWriters.

Since the files/writers are already open, let's rename this to CombineWriteSyncers or ComposeWriteSyncers instead. This function can't fail, so let's also remove the error return value.

writer_test.go Outdated
w, err := OpenWriteSyncers(tw)
if err != nil {
require.NoError(t, err, "OpenWriters Failed.")
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for the conditional.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

writer_test.go Outdated
}

func (w *testWriter) Write(actual []byte) (int, error) {
assert.Equal(w.t, w.expected, actual, "expected writer to write %v, wrote %v", string(w.expected), string(actual))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The failure message automatically contains the expected and actual values, so we can simplify this to

assert.Equal(w.t, w.expected, string(actual), "Unexpected write call.")

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

writer_test.go Outdated
@@ -81,3 +81,28 @@ func TestOpen(t *testing.T) {
assert.Equal(t, tt.filenames, names, "Opened unexpected files given paths %v.", tt.paths)
}
}

type testWriter struct {
expected []byte
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we make this a string, we'll get better output on test failures and easier initialization.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

writer_test.go Outdated

func (w *testWriter) Write(actual []byte) (int, error) {
assert.Equal(w.t, w.expected, actual, "expected writer to write %v, wrote %v", string(w.expected), string(actual))
return 0, nil
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's fulfill the writer contract and return the correct number of bytes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

returning the length of actual

@@ -65,6 +65,12 @@ func TestAddSyncWriter(t *testing.T) {
assert.NoError(t, ws.Sync(), "Unexpected error calling a no-op Sync method.")
}

func TestNewMultiWriteSyncerWorksForSingleWriter(t *testing.T) {
w := AddSync(&bytes.Buffer{})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can use testutils.Buffer here instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@@ -88,6 +88,9 @@ type multiWriteSyncer []WriteSyncer
// NewMultiWriteSyncer creates a WriteSyncer that duplicates its writes
// and sync calls, much like io.MultiWriter.
func NewMultiWriteSyncer(ws ...WriteSyncer) WriteSyncer {
if len(ws) == 1 {
return ws[0]
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Excellent.

@suyash suyash changed the title writer: add OpenWriteSyncers writer: add CombineWriteSyncers Mar 8, 2017
@akshayjshah akshayjshah merged commit 179e456 into uber-go:master Mar 8, 2017
@suyash suyash deleted the open_io_writer branch March 9, 2017 10:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

3 participants